const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

//创建一个微任务
function runMicrotask(fn) {
    //浏览器支持queueMicrotask方法时，使用queueMicrotask方法执行回调函数
    if (typeof queueMicrotask === 'function') {
        queueMicrotask(fn)
    } else if (typeof process === 'object' && typeof process.nextTick === 'function') {
        //nodejs支持process.nextTick方法
        process.nextTick(fn)
    } else if (typeof MutationObserver === 'function') {
        //使用MutationObserver来创建微任务
        const p = document.createElement('p')
        const observer = new MutationObserver(fn)
        observer.observe(p, {
            attributes: true
        })
        p.setAttribute('x-test', '1')
    } else {
        //使用setTimeout来创建微任务
        setTimeout(fn, 0)
    }
}

//判断传入的参数是否是一个符合Promise规范的对象
function isPromiseLike(obj) {
    return typeof obj?.then === 'function'
}

class MyPromise {
    #state = PENDING
    #value
    //handlers 用来存储then方法传入的回调函数 用户可能多次使用then方法传入多个回调函数
    #handlers = []

    constructor(executor) {
        const resolve = (val) => {
            this.#steState(FULFILLED, val)
        }
        const reject = (reason) => {
            this.#steState(REJECTED, reason)
        }
        //代码执行失败时，直接调用reject方法
        try {
            //用户什么时候使用resolve或者reject才会修改promise的状态
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }

    /**
     *状态不是pending时 执行回调函数(说明promise状态变化了 需要执行对应的回调函数)
     *如果此时 状态是pending，那么就先保存回调函数为变量，等状态改变时，再执行回调函数
     *装态更改会在#seteState中调用 所以在#seteState中调用this.#runTask
     */
    #steState(state, val) {
        if (this.#state !== PENDING) return
        this.#state = state
        this.#value = val
        //去执行所有存起来的then方法的回调函数
        this.#runTask()
    }

    #runTask() {
        //我们需要把回调函数放到微任务中执行，因为then方法执行时，promise的状态可能还不是pending，
        // 执行所有保存的then的回调 改方法会在then里调用 也会在#steState(状态改变时)里调用
        runMicrotask(() => {
            if (this.#state !== PENDING) {
                this.#handlers.forEach(handler => {
                    handler()
                })
                this.#handlers = []
            }
        })
    }

    /**
     * 接受回调函数 成功回调和失败回调
     * @param {Function} onFulfilled
     * @param {Function} onRejected
     * @return {MyPromise} 返回新的MyPromise
     * 新的promise做的事就是执行then方法传入的回调函数
     * */
    then(onFulfilled, onRejected) {
        /**
         * 链式调用时 需要执行这里的resolve reject方法 否侧promise不更改状态不会去执行handlers里的函数
         * */
        return new MyPromise((resolve, reject) => {
            this.#handlers.push(() => {
                /**
                 * 根据promise的状态选择选择正确的回调函数,并存入handlers中
                 * 存储then方法传入的回调函数
                 * promise的状态选择调用对应的回调函数,把promise的值作为参数调用回调函数
                 * 并把回调函数的返回值作为参数调用返回的promise的resolve方法
                 * */
                try {
                    const cb = this.#state === FULFILLED ? onFulfilled : onRejected
                    //传入的cb有三种情况 函数 ,不是函数 ,新的promise
                    //函数就执行 拿到结果 不是函数 直接把传入的cb透传出去
                    //这里的res是上一个then的回调函数的返回值
                    const res = typeof cb === 'function' ? cb(this.#value) : this.#value
                    /**
                     * 因为then的回调函数可能返回一个promise 需要判断res是不是promise(通过promise 的A+规范判断)
                     * 如果是直接调用这个返回的promise的then方法，如果不是则调用resolve方法
                     * */
                    if (isPromiseLike(res)) {
                        res.then(resolve, reject)
                    } else {
                        resolve(res)
                    }
                } catch (error) {
                    reject(error)
                }
            })
            //通过调用runTask 去执行所有then方法传入的回调函数
            this.#runTask()
        })
    }

    catch(onRejected) {
        return this.then(null, onRejected)
    }

    //onFinally 不接受任何参数 并且它是透明的不改变元素promise的状态
    finally(onFinally) {
        return this.then((res) => {
            onFinally()
            return res
        }, (err) => {
            onFinally()
            //不能使用return err 因为需要保持状态 需要抛出异常 不这样写then方法里的逻辑永远调用的resolve方法
            throw err
        })
    }

    static resolve(value) {
        //最终都会返回一个promise 如果参数是promise 直接返回
        if (value instanceof Promise) return value
        //创建一个promise返回
        return new Promise((resolve, reject) => {
            // 判断参数是 thenable 对象的话使用它的then方法
            if (isPromiseLike(value)) {
                value.then(resolve, reject)
            } else {
                // 如果不是 thenable 对象的话 返回的 Promise 将会以该值兑现
                resolve(value)
            }
        })
    }

    //与 Promise.resolve() 不同，即使 reason 已经是一个 Promise 对象，Promise.reject() 方法也始终会将其封装在一个新的 Promise 对象中。
    static reject(reason) {
        return new Promise((_, reject) => {
            reject(reason)
        })
    }

    //...args是cb的参数
    static try(cb, ...args) {
        return new Promise((resolve, reject) => {
            /**
             *  try {
             *   resolve(cb())
             *  } catch (error) {
             *   reject(error)
             *  }
             *  因为我们的promise 类的constructor中对改参数函数进行了try catch (executor包裹在try catch中)
             *  所以这里不需要再进行try catch
             * */
            resolve(cb(...args))
        })
    }

    //接受可迭代对象 Array string map set
    static all(promises) {
        //都转换成数组进行处理
        promises = Array.from(promises)
        return new MyPromise((resolve, reject) => {
            //用来存储结果
            const result = []

            if (promises.length === 0) {
                //防止循环为空的情况
                resolve(result)
            }
            //声明计数器 来记录已经完成的promise的数量
            let count = 0
            promises.forEach((promise, index) => {
                //用户可能传入的东西不是promise 使用resolve包裹一下
                MyPromise.resolve(promise).then((res) => {
                    result[index] = res
                    //每次promise完成都会使用then方法 利用计数器 来确定是否完成所有promise
                    count++
                    if (count === promises.length) {
                        resolve(result)
                    }
                    /**
                     * 改参数是 reject 回调函数的引用。直接传递 reject 表示将该回调函数作为参数传递，而不需要立即执行。
                     * 如果写成 reject()，则会在传递时立即执行 reject，导致整个 Promise 立即失败
                     * */
                }, reject)
            })
        })
    }
}

const promise1 = Promise.reject(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "foo");
});

MyPromise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values);
}, (res)=>{
    console.log(res);
})